package org.vacoor.xqq.core.mod.authc.impl;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.vacoor.nothing.common.json.Jacksons;
import org.vacoor.nothing.common.util.Regexs;
import org.vacoor.xqq.core.Constants;
import org.vacoor.xqq.core.bean.Status;
import org.vacoor.xqq.core.http.HttpRequestor;
import org.vacoor.xqq.core.http.Request;
import org.vacoor.xqq.core.http.RequestCallbackAdapter;
import org.vacoor.xqq.core.http.Response;
import org.vacoor.xqq.core.mod.authc.AbstractAuthenticator;
import org.vacoor.xqq.core.mod.authc.AccessToken;
import org.vacoor.xqq.core.mod.authc.AuthenticationToken;
import org.vacoor.xqq.core.mod.authc.exception.AuthenticationException;
import org.vacoor.xqq.core.mod.authc.exception.IncorrectAccountOrCredentialsException;
import org.vacoor.xqq.core.mod.authc.exception.IncorrectCapchaException;
import org.vacoor.xqq.core.mod.client.impl.WebQQClient;
import org.vacoor.xqq.core.util.QQUtil;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

/**
 */
public class WebQQAuthenticator extends AbstractAuthenticator {
    public static Logger logger = LoggerFactory.getLogger(WebQQAuthenticator.class);

    public BufferedImage qrcode() throws IOException {
        HttpURLConnection httpUrlConnection = null;

        try {
            Response response = HttpRequestor.getInstance().send(
                    new Request("https://ssl.ptlogin2.qq.com/ptqrshow?appid=501004106&e=0&l=M&s=5&d=72&v=4&t=0.1", Request.HttpMethod.GET),
                    null
            ).get();
            BufferedImage img = ImageIO.read(response.getContent().asStream());
            return img;
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        } finally {
        }
        return null;
    }
    //用于生成ptqrtoken的哈希函数
    private static int hash33(String s) {
        int e = 0, n = s.length();
        for (int i = 0; n > i; ++i)
            e += (e << 5) + s.charAt(i);
        return 2147483647 & e;
    }

    /**
     * 认证
     * status, ?, url, ?, onMsg, 'nickname/uin'
     * ptuiCB('0','0','http://ptlogin4.web2.qq.com/check_sig?pttype=1&uin=2214963100&service=login&nodirect=0&ptsig=cObkasAuS3HyGCUstbYfDAfe6QAGiAPqSt7cd3rGOao_&s_url=http%3A%2F%2Fw.qq.com%2Fproxy.html%3Flogin2qq%3D1%26webqq_type%3D10&f_url=&ptlang=2052&ptredirect=101&aid=501004106&daid=164&j_later=0&low_login_hour=0&regmaster=0','1','登录成功！', '王测横');
     * ptuiCB('3','0','','0','您输入的帐号或密码不正确，请重新输入。', '2214963100');
     * ptuiCB('4','0','','0','您输入的验证码不正确，请重新输入。', '2214963100');
     * ptuiCB('7','0','','0','很遗憾，网络连接出现异常，请您稍后再试。(3372221109)', '2214963100');
     *
     * @return
     */
    @Override
    protected AccessToken doLogin(AuthenticationToken authcToken) throws AuthenticationException {
        UinPasswordCaptchaStatusToken token = (UinPasswordCaptchaStatusToken) authcToken;
        long uin = token.getUin();
        String captcha = token.getCaptcha();
        String password = QQUtil.encrypt(token.getPassword(), uin, captcha);
        Status status = token.getStatus();
        System.out.println("encrypt:" + password);

        logger.info("发送认证信息..");
        Response response = null;
        try {
            /*
            response = HttpRequestor.getInstance().send(
                    // 均为必填参数
                    new Request(Constants.U_LOGIN, Request.HttpMethod.GET)
                            .addParameter("u", uin)
                            .addParameter("p", password)
                            .addParameter("verifycode", captcha)
                            .addParameter("aid", Constants.APP_ID)
                            .addParameter("h", "1")
                            .addParameter("daid", "164")
                            .addParameter("from_ui", "1")
                            .addParameter("pttype", "1")
                            .addParameter("u1", Constants.U_LOGIN_PROXY)
                            .addParameter("g", "1"),
                    null
            ).get();
            */
            BufferedImage qrcode = qrcode();
            JOptionPane.showMessageDialog(null, "", "", JOptionPane.YES_OPTION, new ImageIcon(qrcode));

            final HttpRequestor requestor = HttpRequestor.getInstance();
            String qrsig = requestor.getCookieValue("qrsig");
            final String serverUrl = ("https://ssl.ptlogin2.qq.com/ptqrlogin?" +
                    "ptqrtoken={1}&webqq_type=10&remember_uin=1&login2qq=1&aid=501004106&" +
                    "u1=http%3A%2F%2Fw.qq.com%2Fproxy.html%3Flogin2qq%3D1%26webqq_type%3D10&" +
                    "ptredirect=0&ptlang=2052&daid=164&from_ui=1&pttype=1&dumy=&fp=loginerroralert&0-0-157510&" +
                    "mibao_css=m_webqq&t=undefined&g=1&js_type=0&js_ver=10184&login_sig=&pt_randsalt=3").replace("{1}", "" + hash33(qrsig));
            response = requestor.send(new Request(serverUrl, Request.HttpMethod.GET), null).get();

            String s = response.getContent().asString();
            logger.debug("认证响应: {}", s);
            String[][] grep = Regexs.grep(s, "ptuiCB\\(\\s*'([0-9])'\\s*,\\s*'([0-9])'\\s*,\\s*'([^']*)'\\s*,\\s*'([0-9])'\\s*,\\s*'([^']*)'\\s*,\\s*'([^']*)'\\s*\\)");

            if (grep.length != 1) {
                throw new AuthenticationException("无法解析响应: " + s);
            }

            // 解析成功
            final int code = Integer.parseInt(grep[0][1]);
            final String url = grep[0][3];
            final String msg = grep[0][5];
            // String nick = grep[0][6];

            switch (code) {
                case 0:     // 登陆成功
                    break;
                case 1:     // 系统繁忙
                case 2:     // 过期的QQ
                    break;
                case 3:     // 账号或密码
                    throw new IncorrectAccountOrCredentialsException(msg);
                case 4:     // 验证码
                    throw new IncorrectCapchaException(msg);
                case 5:     //校验失败
                    break;
                case 6:     // 密码错误, 如果刚刚修改了密码,请稍后再登陆
                    break;
                case 7:     // 网络错误 (有说法是输入有误???)
                case 8:     // IP地址输入错误次数太多, 请稍后再试
                case 24:    // 很遗憾，网络连接出现异常，请您检查是否禁用cookies。(3197108608)', '2214963100'
                default:
                    throw new AuthenticationException(msg);
            }

            /*-
                该请求主要是为了获取所需cookie
                checksig 完成后会进一步重定向以下URL来实现 cookie 跨域, 但是貌似这里不搞也没问题
                "http://w.qq.com/proxy.html?login2qq=1&webqq_type=40"(用于复制web2 cookie)
                在该URL中会创建 iframe src="http://web2.qq.com/web2_cookie_proxy.html"(提供web2 cookie)
            */
            logger.info("校验认证指纹: {}", url);
            HttpRequestor.getInstance().send(new Request(url, Request.HttpMethod.GET), null).get();

            String ptwebqq = HttpRequestor.getInstance().getCookieValue("ptwebqq");
            WebQQAccessToken accessToken = (WebQQAccessToken) WebQQClient.getCurrentClient().getAccessToken();
            accessToken.setClientId(53999199L);
            accessToken.setPtwebqq(ptwebqq);
            accessToken.setStatus(status);


            final String vfWebQQUrl = ("http://s.web2.qq.com/api/getvfwebqq?ptwebqq={1}&clientid=" + accessToken.getClientId() + "&psessionid=&t=0.1").replace("{1}", "");
            Response r = HttpRequestor.getInstance().send(new Request(vfWebQQUrl, Request.HttpMethod.GET).addHeader("Referer", Constants.H_REFERER), null).get();
            String s1 = r.getContent().asString();
            this.vfwebqq = (String) ((Map<String, Object>)Jacksons.deserialize(s1, Map.class).get("result")).get("vfwebqq");

            return linkToServer(accessToken);
        } catch (Throwable e) {
            throw e instanceof AuthenticationException ? (AuthenticationException) e : new AuthenticationException(e);
        }
    }
    private String vfwebqq;

    public WebQQAccessToken linkToServer(WebQQAccessToken accessToken) throws AuthenticationException {
        try {
            Status status = accessToken.getStatus();
            accessToken.setStatus(status = status == null ? Status.ONLINE : status);
            String jsonParam = Jacksons.createObjectNode()
                    .put("ptwebqq", "")
                    .put("clientid", accessToken.getClientId())
                    .put("psessionid", "")
                    .put("status", status.getName()).toString();

            // chain login 重新连接可以直接访问
            String chain = HttpRequestor.getInstance().send(
                    new Request(Constants.U_LOGIN2, Request.HttpMethod.POST)
                            .addHeader("Content-Type", "application/x-www-form-urlencoded")
                            .addHeader("Referer", Constants.H_REFERER)
                            .addHeader("Origin", "http://d1.web2.qq.com")
                            .addHeader("Host", "d1.web2.qq.com")
                            .addParameter("r", jsonParam),
                    null
            ).get().getContent().asString();
            logger.debug("登陆WebQQ服务器: {}", chain);

            /**
             * 0 正常
             * 2 连接错误
             * 4 授权错误
             * 5 密码错误
             * 6 登陆状态解码错误
             */
            // String[][] l2resp = Regexs.grep(chain, "\"uin\":([0-9]+),.*\"vfwebqq\":\"([0-9a-zA-Z]+)\",.*\"psessionid\":\"([0-9a-z]+)\"");
            Map<String, Object> result = (Map) Jacksons.deserialize(chain, Map.class).get("result");

            /*
            if (l2resp.length != 1) {
                throw new AuthenticationException("无法解析:" + chain);
            }
            */

            accessToken.setUin((Long) result.get("uin"));   // TODO 这个应该没有必要了
            // 两个 vfwebqq 不一样,要用一面的.
            accessToken.setVfwebqq(this.vfwebqq);
            // accessToken.setVfwebqq((String) result.get("vfwebqq"));
            accessToken.setPsessionId((String) result.get("psessionid"));

            /*
            accessToken.setUin(Long.parseLong(l2resp[0][1]));   // TODO 这个应该没有必要了
            accessToken.setVfwebqq(l2resp[0][2]);
            accessToken.setPsessionId(l2resp[0][3]);
            */
            accessToken.setAuthenticated(true);

            String x = "http://d1.web2.qq.com/channel/get_online_buddies2?vfwebqq=" + accessToken.getVfwebqq() + "&clientid=53999199&psessionid=" + accessToken.getPsessionId() + "&t=0.1";
            String xx = HttpRequestor.getInstance().send(
                    new Request(x, Request.HttpMethod.GET)
                            .addHeader("Referer", Constants.H_REFERER)
                            .addHeader("Origin", "http://d1.web2.qq.com")
                            .addHeader("Host", "d1.web2.qq.com")
                            .addParameter("r", jsonParam),
                    null
            ).get().getContent().asString();

            return accessToken;
        } catch (Throwable t) {
            throw t instanceof AuthenticationException ? (AuthenticationException) t : new AuthenticationException(t);
        }
    }


    @Override
    public void logout() {
        WebQQClient client = WebQQClient.getCurrentClient();
        WebQQAccessToken token = (WebQQAccessToken) client.getAccessToken();
        if (!token.isAuthenticated()) {
            return;
        }
        try {
            token.setAuthenticated(false);
            HttpRequestor.getInstance().send(
                    new Request(Constants.U_LOGOUT, Request.HttpMethod.GET)
                            .addHeader("Referer", Constants.H_REFERER)
                            .addParameter("clientid", token.getClientId())
                            .addParameter("psessionid", token.getPsessionId())
                            .addParameter("ids", "")
                            .addParameter("ui", new Date().getTime()),
                    new RequestCallbackAdapter() {
                        @Override
                        public void onSuccess(Response resp) {
                            logger.debug("登出, 响应: {}", resp.getContent().asString());
                        }
                    }
            ).get();
        } catch (Throwable ignore) {
        }
    }
}
